package driver;

import java.util.LinkedList;

import state.Globals;

import driver.actions.BaseAction;

/**
 * This class is used to delay actions from being scheduled in the Action Driver
 * 
 * @author Ian Doerr
 */
public class ActionDelayQueue implements Runnable {
    
    /**
     * This class is used to record when an action should be executed
     * 
     * @author Ian
     */
    private class TimeWrap
    {
        public long executeTime;
        public BaseAction action;
    }
    
    private boolean shouldContinue = true;
    private Thread runThread;
    
    private LinkedList<TimeWrap> actions = new LinkedList<TimeWrap>();
    
    /**
     * Initializes the Action Delay Queue
     */
    public ActionDelayQueue()
    {
        runThread = new Thread(this);
    }
    
    /**
     * Wraps the given action, and inserts it to the time queue based on when it 
     * is expected to exeecute 
     * 
     * @param delay
     * @param action
     */
    public synchronized void appendAction(long delay, BaseAction action)
    {
        TimeWrap wrap = new TimeWrap();
        wrap.executeTime = System.currentTimeMillis() + delay; // calculate when this action should be executed.
        wrap.action = action;
        
        int i = 0;
        
        //basic insert operation, assuming an ascending-ordered list
        for (TimeWrap w : actions)
        {
            if (w.executeTime > wrap.executeTime)
            {
                break;
            }
            i++;
        }
        
        actions.add(i, wrap);
        
        if (i == 0)
        {
            notify();
        }
    }
    
    /**
     * Starts the runner thread
     */
    public void start()
    {
        runThread.start();
    }
    
    @Override
    /**
     * This is the main run method, that waits on the queue until it completes
     */
    public void run()
    {
        while (shouldContinue)
        {
            synchronized (this)
            {
                if (actions.isEmpty()) // wait for a new action
                {
                    try
                    {
                        wait();
                    }
                    catch (InterruptedException e)
                    {
                        Globals.getLogger().log("Got interrupted while waiting!", "ActionDelayQueue", "run");
                    }
                }
                else if (System.currentTimeMillis() >= actions.get(0).executeTime) //execute a new action if the time has expired
                {
                  	ActionDriver.getInstance().executeAction(0, actions.remove(0).action);
                }
                else //otherwise, wait until the next action is expected to execute
                {
                    try
                    {
                        wait(actions.get(0).executeTime - System.currentTimeMillis());
                    }
                    catch (InterruptedException e)
                    {
                        Globals.getLogger().log("Got interrupted while waiting!", "ActionDelayQueue", "run");
                    }
                }
            }
        }
    }
    
    /**
     * Shuts down this thread, and returns once the thread has successfully shut down.
     */
    public void shutdown()
    {
        shouldContinue = false;
        
        synchronized (this)
        {
            notify();
        }
        
        try
        {
            runThread.join();
        }
        catch (InterruptedException e)
        {
            Globals.getLogger().log("Got interrupted while joining the Thread!", "ActionDelayQueue", "shutdown");
        }
    }
    
}
